---
title: Image Editor
sidebar_position: 3
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import {ImageEditor} from '../../src/components/tutorials/ImageEditor/ImageEditor.tsx'

## Idea

This tutorial is not designated to describe the creating image editor from the scratch.
All components already created and provided here. This tutorial is about the creating basic
image editor functionality and integration one with the cropper.

<ImageEditor/>

## Template

The used components are available in [the documentation repository](https://github.com/Norserium/react-advanced-cropper/tree/master/example/src/components/tutorials/ImageEditor/).

<Tabs>
<TabItem value="tsx" label="TSX" default>

:::note
To make this code works for Safari the polyfill is needed. Otherwise you can use any library
that implements custom filters for canvas.

You can use the [context-filter-polyfill](https://www.npmjs.com/package/context-filter-polyfill) for example:
```
yarn add context-filter-polyfill
```
:::

```tsx
import React, { useState } from 'react';
import { Navigation } from './components/Navigation';
import { Slider } from './components/Slider';
import './ImageEditor.scss';

// The polyfill for Safari browser. The dynamic require is needed to work with SSR
if (typeof window !== 'undefined') {
	require('context-filter-polyfill');
}

export const ImageEditor = () => {
	const [mode, setMode] = useState('crop');
	const [adjustments, setAdjustments] = useState({
		brightness: 0,
		hue: 0,
		saturation: 0,
		contrast: 0,
	});

	const onChangeValue = (value: number) => {
		if (mode in adjustments) {
			setAdjustments((previousValue) => ({
				...previousValue,
				[mode]: value,
			}));
		}
	};

	return (
		<div className={'image-editor'}>
			<div className="image-editor__cropper">
				{mode !== 'crop' && (
					<Slider className="image-editor__slider" value={adjustments[mode]} onChange={onChangeValue} />
				)}
			</div>
			<Navigation mode={mode} onChange={setMode} />
		</div>
	);
};
```

</TabItem>
<TabItem value="styles" label="Styles">

```scss
.image-editor {
	color: #61DAFB;
	border: solid 1px #2b2a30;
	&__cropper {
		background: #0f0e13;
		height: 500px;
		max-height: 100vh;
		position: relative;
	}
	&__slider {
		width: 100%;
		left: 50%;
		transform: translateX(-50%);
		bottom: 20px;
		position: absolute;
	}
}
```

</TabItem>
</Tabs>

## Integrate the cropper

Let's add the cropper to the template above. It's trivial, but we need to block the cropper if an user
choice any other mode than `crop`.


<Tabs>
<TabItem value="tsx" label="TSX" default>

```tsx
import React, { useState } from 'react';
import { Cropper } from 'react-advanced-cropper';
import { Navigation } from './components/Navigation';
import { Slider } from './components/Slider';
import './ImageEditor.scss';

export const ImageEditor = () => {
	const [src, setSrc] = useState(
		'https://images.pexels.com/photos/12051260/pexels-photo-12051260.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260',
	);
	const [mode, setMode] = useState('crop');
	const [adjustments, setAdjustments] = useState({
		brightness: 0,
		hue: 0,
		saturation: 0,
		contrast: 0,
	});

	const onChangeValue = (value: number) => {
		if (mode in adjustments) {
			setAdjustments((previousValue) => ({
				...previousValue,
				[mode]: value,
			}));
		}
	};

	const onUpload = (blob: string) => {
		setAdjustments({
			brightness: 0,
			hue: 0,
			saturation: 0,
			contrast: 0,
		});
		setMode('crop');
		setSrc(blob);
	};

	const onDownload = () => {
		if (cropperRef.current) {
			const newTab = window.open();
			if (newTab) {
				newTab.document.body.innerHTML = `<img src="${cropperRef.current.getCanvas()?.toDataURL()}"/>`;
			}
		}
	};

	const cropperEnabled = mode === 'crop';

	return (
		<div className={'image-editor'}>
			<div className="image-editor__cropper">
				<Cropper
					src={src}
					stencilProps={{
						movable: cropperEnabled,
						resizable: cropperEnabled,
						lines: cropperEnabled,
						handlers: cropperEnabled,
						overlayClassName: cn(
							'image-editor__cropper-overlay',
							!cropperEnabled && 'image-editor__cropper-overlay--faded',
						),
					}}
					backgroundWrapperProps={{
						scaleImage: cropperEnabled,
						moveImage: cropperEnabled,
					}}
				/>
				{mode !== 'crop' && (
					<Slider className="image-editor__slider" value={adjustments[mode]} onChange={onChangeValue} />
				)}
			</div>
			<Navigation mode={mode} onChange={setMode} onUpload={onUpload} onDownload={onDownload} />
		</div>
	);
};
```

</TabItem>
<TabItem value="styles" label="Styles">

```scss
.image-editor {
	color: #61DAFB;
	border: solid 1px #2b2a30;
	&__cropper {
		background: #0f0e13;
		height: 500px;
		max-height: 100vh;
		position: relative;
	}
	&__slider {
		width: 100%;
		left: 50%;
		transform: translateX(-50%);
		bottom: 20px;
		position: absolute;
	}
	&__cropper-overlay {
		transition: 0.5s;
		&--faded {
			color: rgba(black, 0.9);
		}
	}
}
```

</TabItem>
</Tabs>

But how we will adjust an uploaded image? That's a tough question.

1. We can change `src` and pass to it the Blob or DataURL of changed images.
The problem here that cropper will reset all of our changes. The performance of this solution will be terrible alike.

2. We can replace `src` by using of `setImage` method. If you use this method to change the actual cropper image it won't reset the changes, so it solves one of the problems. But performance will be still awful.

3. The right way is the replacing the default background component by a custom one.

### Custom background component

The background component is the special component that uses by the cropper to:
- display the current image
- retrieve the ref to content (`HTMLImageElement` or `HTMLCanvasElement`) to pass it to its own canvas and crop it

The default background component is `CropperBackgroundImage`. It's pretty straightforward.


<Tabs>
<TabItem value="tsx" label="TSX" default>


```tsx
import React, { forwardRef, Event } from 'react';
import cn from 'classnames';
import {
	CropperTransitions,
	CropperImage,
	CropperState,
	getBackgroundStyle
} from 'react-advanced-cropper'

interface Props {
	className?: string;
	image: CropperImage | null;
	state: CropperState | null;
	transitions?: CropperTransitions;
	crossOrigin?: 'anonymous' | 'use-credentials' | boolean;
}

export const CropperBackgroundImage = forwardRef<HTMLImageElement, Props>(
	({ className, image, state, crossOrigin, transitions}: Props, ref) => {
		const style = image && state ? getBackgroundStyle(image, state, transitions) : {};

		const src = image ? image.src : undefined;

		return src ? (
			<img
				key={src}
				ref={ref}
				className={cn('react-cropper-background-image', className)}
				src={src}
				crossOrigin={crossOrigin === true ? 'anonymous' : crossOrigin || undefined}
				style={style}
				onMouseDown={(e: Event) => e.preventDefault()}
			/>
		) : null;
	},
);

CropperBackgroundImage.displayName = 'CropperBackgroundImage';
```

</TabItem>
<TabItem value="styles" label="Styles">

```scss
.react-cropper-background-image {
	user-select: none;
	position: absolute;
	transform-origin: center;
	pointer-events: none;
	// Workaround to prevent bugs at the websites with max-width
	// rule applied to img (Vuepress for example)
	max-width: none !important;
}
```

</TabItem>
</Tabs>

Like we said before, we can forward ref to either `HTMLImageElement` or `HTMLCanvasElement` elements. The last one can be used to draw image with all adjustments
that we made. It's performant and very flexible way.

Let's do it.

First of all we should create adjustable image, that will be an alternative for `<img/>` tag.advanced



<Tabs>
<TabItem value="tsx" label="TSX" default>


```tsx
import React, { forwardRef, useRef, CSSProperties, useLayoutEffect } from 'react';
import cn from 'classnames';
import { mergeRefs } from 'react-advanced-cropper';
import './AdjustableImage.scss';

interface Props {
	src?: string;
	className?: string;
	crossOrigin?: 'anonymous' | 'use-credentials' | boolean;
	brightness?: number;
	saturation?: number;
	hue?: number;
	contrast?: number;
	style?: CSSProperties;
}

export const AdjustableImage = forwardRef<HTMLCanvasElement, Props>(
	({ src, className, crossOrigin, brightness = 0, saturation = 0, hue = 0, contrast = 0, style }: Props, ref) => {
		const imageRef = useRef<HTMLImageElement>(null);
		const canvasRef = useRef<HTMLCanvasElement>(null);

		const drawImage = () => {
			const image = imageRef.current;
			const canvas = canvasRef.current;
			if (canvas && image && image.complete) {
				const ctx = canvas.getContext('2d');
				canvas.width = image.naturalWidth;
				canvas.height = image.naturalHeight;

				if (ctx) {
					ctx.filter = [
						`brightness(${100 + brightness * 100}%)`,
						`contrast(${100 + contrast * 100}%)`,
						`saturate(${100 + saturation * 100}%)`,
						`hue-rotate(${hue * 360}deg)`,
					].join(' ');

					ctx.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight);
				}
			}
		};

		useLayoutEffect(() => {
			drawImage();
		}, [src, brightness, saturation, hue, contrast]);

		return (
			<>
				<canvas
					key={`${src}-canvas`}
					ref={mergeRefs([ref, canvasRef])}
					className={cn('adjustable-image-element', className)}
					style={style}
				/>
				{src ? (
					<img
						key={`${src}-img`}
						ref={imageRef}
						className={'adjustable-image-source'}
						src={src}
						crossOrigin={crossOrigin === true ? 'anonymous' : crossOrigin || undefined}
						onLoad={drawImage}
					/>
				) : null}
			</>
		);
	},
);

AdjustableImage.displayName = 'AdjustableImage';
```

</TabItem>
<TabItem value="styles" label="Styles">

```scss
.adjustable-image-source {
	display: none;
}
```

</TabItem>
</Tabs>


Then, we will use this component to create the custom cropper background image component.

<Tabs>
<TabItem value="tsx" label="TSX" default>

```tsx
import React, { forwardRef } from 'react';
import { CropperTransitions, CropperImage, CropperState } from 'react-advanced-cropper';
import { getBackgroundStyle } from 'advanced-cropper';
import { AdjustableImage } from './AdjustableImage';

interface DesiredCropperRef {
	getState: () => CropperState;
	getTransitions: () => CropperTransitions;
	getImage: () => CropperImage;
}

interface Props {
	className?: string;
	cropper: DesiredCropperRef;
	crossOrigin?: 'anonymous' | 'use-credentials' | boolean;
	brightness?: number;
	saturation?: number;
	hue?: number;
	contrast?: number;
}

export const AdjustableCropperBackground = forwardRef<HTMLCanvasElement, Props>(
	({ className, cropper, crossOrigin, brightness = 0, saturation = 0, hue = 0, contrast = 0 }: Props, ref) => {
		const state = cropper.getState();
		const transitions = cropper.getTransitions();
		const image = cropper.getImage();

		const style = image && state ? getBackgroundStyle(image, state, transitions) : {};

		return (
			<AdjustableImage
				src={image?.src}
				crossOrigin={crossOrigin}
				brightness={brightness}
				saturation={saturation}
				hue={hue}
				contrast={contrast}
				ref={ref}
				className={className}
				style={style}
			/>
		);
	},
);
```

</TabItem>
</Tabs>

Let's use then the created background component in our image editor:


```tsx
<Cropper
	...
	backgroundComponent={AdjustableCropperBackground}
	backgroundProps={adjustments}
/>
```

## Preview

What's about the cropper preview? The `CropperPreview` component is pretty similar to the cropper itself by its structure.

First of all, you should create the custom preview background component.

<Tabs>
<TabItem value="tsx" label="TSX" default>

```tsx
import React from 'react';
import { CropperTransitions, CropperImage, CropperState, Size } from 'react-advanced-cropper';
import { getPreviewStyle } from 'advanced-cropper';
import { AdjustableImage } from './AdjustableImage';

interface DesiredCropperRef {
	getState: () => CropperState;
	getTransitions: () => CropperTransitions;
	getImage: () => CropperImage;
}

interface Props {
	className?: string;
	cropper: DesiredCropperRef;
	crossOrigin?: 'anonymous' | 'use-credentials' | boolean;
	brightness?: number;
	saturation?: number;
	hue?: number;
	contrast?: number;
	size?: Size | null;
}

export const AdjustablePreviewBackground = ({
	className,
	cropper,
	crossOrigin,
	brightness = 0,
	saturation = 0,
	hue = 0,
	contrast = 0,
	size,
}: Props) => {
	const state = cropper.getState();
	const transitions = cropper.getTransitions();
	const image = cropper.getImage();

	const style = image && state && size ? getPreviewStyle(image, state, size, transitions) : {};

	return (
		<AdjustableImage
			src={image?.src}
			crossOrigin={crossOrigin}
			brightness={brightness}
			saturation={saturation}
			hue={hue}
			contrast={contrast}
			className={className}
			style={style}
		/>
	);
};
```

</TabItem>
</Tabs>

Then just pass it to `CropperPreview`:

```tsx
<CropperPreview
	...
	backgroundComponent={AdjustablePreviewBackground}
	backgroundProps={adjustments}
/>
```

After that you will get the same image editor like you see in the start of tutorial. The full source code of the image editor is available in [the following sandbox](https://codesandbox.io/s/react-advanced-cropper-image-editor-90gs8z?file=/src/index.tsx).




